Pick<T, K>
은 객체 타입에서 원하는 키만 선택할 수 있도록 해주지만, 여러 키를 동시에 선택 가능합니다.
하지만 때로는 객체의 여러 속성 중 "정확히 하나만" 가질 수 있도록 강제하고 싶은 경우가 있습니다.
이럴 때 사용가능한 커스텀 유틸 타입을 만들 수 있습니다.
API나 함수에서 조건 중 하나만 입력받도록 강제하고 싶을 때 PickOne
이 매우 유용합니다.
사용자가 사람을 검색할 때 이름, 이메일, 전화번호 중 하나의 조건만 사용 가능해야 한다고 가정
type SearchFilter = {
name: string;
email: string;
phone: string;
};
type FilterInput = PickOne<SearchFilter>;
function searchUser1(filter: SearchFilter) {
// 여러 조건이 입력되어도 타입 에러가 발생하지 않음
}
function searchUser2(filter: FilterInput) {
// 정확히 하나의 조건만 입력되어야 함
}
searchUser1({ name: "Alice" }); // ✅ 허용
searchUser1({ email: "alice@test.com" }); // ✅ 허용
searchUser1({ phone: "010-1234-5678" }); // ✅ 허용
searchUser2({ name: "Alice" }); // ✅ 허용
searchUser2({ email: "alice@test.com" }); // ✅ 허용
searchUser2({ phone: "010-1234-5678" }); // ✅ 허용
searchUser1({ name: "Alice", email: "alice@test.com" }); // ❗ 의도와 다르게 타입 에러 없음
searchUser2({ name: "Alice", email: "alice@test.com" }); // ❌ 오류: 둘 다 있으면 안 됨
✔️ 이메일과 전화번호가 동시에 들어오면 API가 모호해지기 때문에, 타입 수준에서 명확하게 하나만 허용되도록 제한하는 것이 한정하고 의도를 명확이 전달할 수 있는 방법입니다.
PickOne<T>
타입 정의type PickOne<T> = {
[P in keyof T]: Record<P, P[T]> & Partial<Record<Exclude<keyof T, P>, undefined>>;
}[keyof T]
Record<P, T[P]>
- 현재 Key만 포함한 객체 만들기예: P = "name"
이면
Record<"name", string> // => { name: string }
✔️ key P
는 필수로 포함된 객체
Exclude<keyof T, P>
- 현재 key를제외한 나머지 key 추출하기예: T = {name, email, phone}
이고, P = "name"
이면
Exclude<"name" | "email" | "phone", "name">
// => "email" | "phone"
✔️ P
를 제외한 나머지 키들만 추출
Record<Exclude<keyof T, P>, undefined>
- 나머지 키들은 undefined만 허용Record<"email" | "phone", undefined>
// => { email: undefined; phone: undefined }
Partial<Record<Exclude<keyof T, P>, undefined>>
- 나머지 키들은 optional + undefinedPartial<{ email: undefined; phone: undefined }>
// => { email?: undefined; phone?: undefined }
✔️ 해당 하는 키들은 없거나 undefined
만 가능
Record<P, T[P]> & Partial<...>
현재 키만 필수 + 나머지 키는 optional or undefined예: P = "name"
{
name: string; // 필수
email?: undefined; // 없어도 되고, 있으면 undefined
phone?: undefined; // 없어도 되고, 있으면 undefined
}
[P in keyof T] ... [keyof T]
- 위 구조를 모든 키마다 만들어서 유니온으로 묶기{
[P in keyof T]: ...
}[keyof T]
예: T = {name: string, email: string, phone: string}
type PickOne<T> =
| { name: string; email?: undefined; phone?: undefined }
| { name?: undefined; email: string; phone?: undefined }
| { name?: undefined; email?: undefined; phone: string }
✔️ T의 각 key마다 하나씩만 필수로 있고, 나머지는 optional undefined인 구조로 된 유니온 타입이 만들어집니다.
✔️ 정확히 하나만 값이 있는 객체 구조가 됩니다.